/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author           Notes
 * 2019-07-06     heyuanjie87      first version
 */

#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>

#define DBG_ENABLE
#define DBG_LVL DBG_LOG
#define DBG_TAG "UART"
#include <rtdbg.h>

#ifdef RT_USING_POSIX
#include <dfs_posix.h>
#include <dfs_poll.h>

#ifdef RT_USING_POSIX_TERMIOS
#include <posix_termios.h>
#endif

#ifndef RT_SERIAL_RB_BUFSZ
#define RT_SERIAL_RB_BUFSZ 64
#endif

#ifdef SERIAL_THREAD_SAFE
#define _lock_read(s, nb)                    \
    if (nb)                                  \
    {                                        \
        if (rt_mutex_take(s->rlock, 0) != 0) \
            return -EAGAIN;                  \
    }                                        \
    else                                     \
    {                                        \
        rt_mutex_take(s->rlock, -1);         \
    }

#define _unlock_read(s) rt_mutex_release(s->rlock);

#define _lock_write(s, nb)                   \
    if (nb)                                  \
    {                                        \
        if (rt_mutex_take(s->wlock, 0) != 0) \
            return -EAGAIN;                  \
    }                                        \
    else                                     \
    {                                        \
        rt_mutex_take(s->wlock, -1);         \
    }

#define _unlock_write(s) rt_mutex_release(s->wlock);
#else
#define _lock_read(s, nb)
#define _unlock_read(s)
#define _lock_write(s, nb)
#define _unlock_write(s)
#endif

static int _set_tx_enable(rt_serial_t *s, int en)
{
    int ret = 0;

    if (!s->tx_started && en)
    {
        s->tx_started = 1;
        ret = s->ops->xmit_ctl(s, UART_XMIT_TX, en);
    }
    else if (!en)
    {
        ret = s->ops->xmit_ctl(s, UART_XMIT_TX, en);
        s->tx_started = 0;
    }

    return ret;
}

static int _set_rx_enable(rt_serial_t *s, int en)
{
    return s->ops->xmit_ctl(s, UART_XMIT_RX, en);
}

static int _resize_obuf(rt_serial_t *s, int size)
{
    struct rt_ringbuffer *rb, *pre;
    rt_base_t level;

    pre = s->txrb;
    if (size < RT_SERIAL_RB_BUFSZ || size > 2048)
        return -EINVAL;
    if (size == pre->buffer_size)
        return 0;

    rb = rt_ringbuffer_create(size);
    if (!rb)
        return -ENOMEM;

    _lock_write(s, 0);

    level = rt_hw_interrupt_disable();
    s->txrb = rb;
    rt_hw_interrupt_enable(level);

    _unlock_write(s);

    rt_ringbuffer_destroy(pre);

    return 0;
}

static int _resize_ibuf(rt_serial_t *s, int size)
{
    struct rt_ringbuffer *rb, *pre;
    rt_base_t level;

    pre = s->rxrb;
    if (size < RT_SERIAL_RB_BUFSZ || size > 2048)
        return -EINVAL;
    if (size == pre->buffer_size)
        return 0;

    rb = rt_ringbuffer_create(size);
    if (!rb)
        return -ENOMEM;

    _lock_read(s, 0);

    level = rt_hw_interrupt_disable();
    s->rxrb = rb;
    rt_hw_interrupt_enable(level);

    _unlock_read(s);

    rt_ringbuffer_destroy(pre);

    return 0;
}

static int _set_halfduplex(rt_serial_t *s, uint8_t en)
{
    s->config.halfduplex = en;

    return s->ops->configure(s, &s->config);
}

static int _uart_init(rt_serial_t *s)
{
    int ret = -EIO;

    if (s->parent.ref_count)
    {
        s->parent.ref_count++;
        return 0;
    }
    if (!s->ops->init)
        return -ENOSYS;

    s->rxrb = rt_ringbuffer_create(RT_SERIAL_RB_BUFSZ);
    s->txrb = rt_ringbuffer_create(RT_SERIAL_RB_BUFSZ);
    if (!s->rxrb || !s->txrb)
    {
        ret = -ENOMEM;
        goto _free;
    }

    rt_wqueue_init(&s->txwq);
    rt_wqueue_init(&s->rxwq);

    s->config.halfduplex = 0;
    s->config.baud_rate = 115200;
    s->config.data_bits = DATA_BITS_8;
    s->config.parity = PARITY_NONE;
    s->config.stop_bits = STOP_BITS_1;
    s->tx_started = 0;

    if (s->ops->init(s) != 0)
        goto _free;

    if ((s->ops->configure(s, &s->config) == 0) &&
        (_set_rx_enable(s, 1) == 0))
    {
        s->parent.ref_count++;
        return 0;
    }

    if (s->ops->deinit)
        s->ops->deinit(s);
_free:
    rt_ringbuffer_destroy(s->rxrb);
    rt_ringbuffer_destroy(s->txrb);

    return ret;
}

static int _raw_read(rt_serial_t *s, int nb, uint8_t *buf, size_t size)
{
    int ret = 0;
    int c;

    while (1)
    {
        c = rt_ringbuffer_get(s->rxrb, buf, size);
        if (c == 0)
        {
            if (nb && (ret == 0))
            {
                ret = -EAGAIN;
                break;
            }
            if (ret)
                break;

            rt_wqueue_wait(&s->rxwq, 0, -1);
        }
        else
        {
            size -= c;
            buf += c;
            ret += c;
        }
    }

    return ret;
}

static int _raw_write(rt_serial_t *s, int nb, const uint8_t *buf, size_t size)
{
    int ret = -EAGAIN;
    int c;
    const uint8_t *dbuf;

    dbuf = buf;

    if (!s->tx_started)
    {
        /* when Tx is idle, write hardware UART-FIFO first */
        while (size && (s->ops->put(s, *dbuf) == 1))
        {
            dbuf++;
            size--;
        }
    }

    while (size)
    {
        while (size > 0)
        {
            c = rt_ringbuffer_put(s->txrb, dbuf, size);
            if (!c)
                break;

            if (_set_tx_enable(s, 1) != 0)
            {
                ret = -EIO;
                break;
            }

            dbuf += c;
            size -= c;
        }

        if (nb)
            break;

        /* 非阻塞模式等待数据发完后才返回 */
        rt_wqueue_wait(&s->txwq, 0, -1);
    }

    return (dbuf == buf) ? ret : (dbuf - buf);
}

#ifdef RT_USING_POSIX_TERMIOS
#define O_OPOST(tty) ((tty)->config.c_oflag & OPOST)
#define O_ONLCR(tty) ((tty)->config.c_oflag & ONLCR)

static int do_output_char(rt_serial_t *serial, unsigned char c, int space)
{
    int spaces;

    if (!space)
        return -1;

    switch (c)
    {
    case '\n':
        if (O_ONLCR(serial))
        {
            if (space < 2)
                return -1;

            rt_ringbuffer_put(serial->txrb, "\r\n", 2);
            return 2;
        }
        break;
    default:
        break;
    }

    return 1;
}

static int process_output_char(rt_serial_t *serial, unsigned char c)
{
    int space, retval;

    space = rt_ringbuffer_space_len(serial->txrb);
    retval = do_output_char(serial, c, space);

    if (retval < 0)
        return -1;
    else
        return 0;
}

static int process_output_block(rt_serial_t *serial, const unsigned char *buf, unsigned int nr)
{
    int space;
    int i;
    const unsigned char *cp;

    space = rt_ringbuffer_space_len(serial->txrb);
    if (!space)
    {
        return -1;
    }

    if (nr > space)
        nr = space;

    for (i = 0, cp = buf; i < nr; i++, cp++)
    {
        unsigned char c = *cp;

        switch (c)
        {
        case '\n':
            if (O_ONLCR(serial))
                goto break_out;
            break;
        case '\r':
            break;
        case '\t':
            goto break_out;
        case '\b':
            break;
        default:
            break;
        }
    }

break_out:
    i = rt_ringbuffer_put(serial->txrb, buf, i);

    return i;
}

static int _post_write(rt_serial_t *s, int nb, const uint8_t *buf, size_t size)
{
    int ret = -EAGAIN;
    int c;
    const uint8_t *dbuf;

    dbuf = buf;
    while (1)
    {
        while (size > 0)
        {
            int num;

            num = process_output_block(s, dbuf, size);
            if (num < 0)
                break;

            dbuf += num;
            size -= num;
            if (size == 0)
            {
                break;
            }

            c = *dbuf;
            if (process_output_char(s, c) < 0)
                break;
            dbuf++;
            size--;
        }

        if (_set_tx_enable(s, 1) != 0)
            return -EIO;

        if (!size)
            break;
        if (nb)
            break;

        rt_wqueue_wait(&s->txwq, 0, -1);
    }

break_out:
    return (dbuf == buf) ? ret : (dbuf - buf);
}

struct tlbaud
{
    uint32_t tb;
    uint32_t lb;
};

static const struct tlbaud _baud[] = {
    {B4800, 4800}, {B9600, 9600}, {B38400, 38400}, {B57600, 57600}, {B115200, 115200}, {B2000000, 2000000}};

static void _cfg_to_termios(struct termios *term, struct serial_configure *cfg)
{
    int i;

    term->c_oflag = cfg->c_oflag;
    if (cfg->stop_bits == STOP_BITS_2)
        term->c_cflag |= CSTOPB;
    else
        term->c_cflag &= ~CSTOPB;

    if (cfg->parity == PARITY_NONE)
    {
        term->c_cflag &= ~PARENB;
    }
    else
    {
        term->c_cflag |= PARENB;
        if (cfg->parity == PARITY_ODD)
            term->c_cflag |= PARODD;
        else
            term->c_cflag &= ~PARODD;
    }

    term->c_cflag &= ~CBAUD;
    for (i = 0; i < sizeof(_baud) / sizeof(struct tlbaud); i++)
    {
        if (_baud[i].lb == cfg->baud_rate)
        {
            term->c_cflag |= _baud[i].tb;
            break;
        }
    }
}

static int _get_termios(rt_serial_t *s, struct termios *t)
{
    _cfg_to_termios(t, &s->config);

    return 0;
}

static void _termios_to_cfg(struct termios *term, struct serial_configure *cfg)
{
    int i;

    cfg->c_oflag = term->c_oflag;
    if (term->c_cflag & CSTOPB)
        cfg->stop_bits = STOP_BITS_2;
    else
        cfg->stop_bits = STOP_BITS_1;

    if (term->c_cflag & PARENB)
    {
        if (term->c_cflag & PARODD)
            cfg->parity = PARITY_ODD;
        else
            cfg->parity = PARITY_EVEN;
    }
    else
    {
        cfg->parity = PARITY_NONE;
    }

    cfg->data_bits = DATA_BITS_8;

    if ((term->c_cflag & CBAUD) == CBAUDEX)
    {
        /* custom baud */
        cfg->baud_rate = term->__c_ispeed;
    }
    else
    {
        for (i = 0; i < sizeof(_baud) / sizeof(struct tlbaud); i++)
        {
            if ((term->c_cflag & CBAUD) == _baud[i].tb)
            {
                cfg->baud_rate = _baud[i].lb;
                break;
            }
        }
    }
}

static int _set_termios(rt_serial_t *s, struct termios *t)
{
    _termios_to_cfg(t, &s->config);

    return s->ops->configure(s, &s->config);
}

static int _clear_buf(rt_serial_t *s, int q)
{
    int ret = 0;

    switch (q)
    {
    case TCIFLUSH:
        rt_ringbuffer_reset(s->rxrb);
        break;
    case TCOFLUSH:
        rt_ringbuffer_reset(s->txrb);
        break;
    case TCIOFLUSH:
        rt_ringbuffer_reset(s->rxrb);
        rt_ringbuffer_reset(s->txrb);
        break;
    default:
        ret = -EINVAL;
        break;
    }

    return ret;
}
#endif

/* fops for serial */
static int serial_fops_open(struct dfs_fd *fd, ...)
{
    int ret;
    rt_serial_t *s;

    s = fd->data;
    ret = _uart_init(s);

    return ret;
}

static int serial_fops_close(struct dfs_fd *fd)
{
    int ret;

    return ret;
}

static int serial_fops_ioctl(struct dfs_fd *fd, int cmd, void *args)
{
    int ret;
    rt_serial_t *s;

    s = fd->data;

    switch (cmd & 0xFFFF)
    {
#ifdef RT_USING_POSIX_TERMIOS
    case TCGETA:
    case TCGETS:
    {
        ret = _get_termios(s, args);
    }
    break;
    case TCSETS:
    case TCSETA:
    {
        ret = _set_termios(s, args);
    }
    break;
    case TCFLSH:
    {
        ret = _clear_buf(s, (int)args);
    }
    break;
#endif
    case TIOCSOBUFSZ:
    {
        ret = _resize_obuf(s, (int)args);
    }
    break;
    case TIOCSIBUFSZ:
    {
        ret = _resize_ibuf(s, (int)args);
    }
    break;
    case TIOCSERHD:
    {
        ret = _set_halfduplex(s, (uint8_t)args);
    }
    break;
    default:
    {
        ret = s->ops->control(s, cmd, args);
    }
    break;
    }

    return ret;
}

static int serial_fops_read(struct dfs_fd *file, void *buf, size_t size, ...)
{
    int ret;
    rt_serial_t *s;
    int nb;

    if (size == 0)
        return 0;

    s = file->data;
    nb = file->flags & O_NONBLOCK;

    _lock_read(s, nb);

    ret = _raw_read(s, nb, (uint8_t *)buf, size);

    _unlock_read(s);

    return ret;
}

static int serial_fops_write(struct dfs_fd *file, const void *buf, size_t size, ...)
{
    int ret;
    rt_serial_t *s;
    int nb;

    if (size == 0)
        return 0;

    s = file->data;
    nb = file->flags & O_NONBLOCK;

    _lock_write(s, nb);

#ifdef RT_USING_POSIX_TERMIOS
    if (O_OPOST(s))
        ret = _post_write(s, nb, (const uint8_t *)buf, size);
    else
        ret = _raw_write(s, nb, (const uint8_t *)buf, size);
#else
    ret = _raw_write(s, nb, (const uint8_t *)buf, size);
#endif

    _unlock_write(s);

    return ret;
}

static int serial_fops_poll(struct dfs_fd *fd, struct rt_pollreq *req)
{
    int mask = 0;
    rt_serial_t *s;

    s = (rt_serial_t *)fd->data;

    if (rt_ringbuffer_data_len(s->rxrb) != 0)
    {
        mask |= POLLIN;
    }
    else
    {
        rt_poll_add(&s->rxwq, req);
    }

    if (rt_ringbuffer_space_len(s->txrb) != 0)
    {
        mask |= POLLOUT;
    }
    else
    {
        rt_poll_add(&s->txwq, req);
    }

    return mask;
}

const static struct dfs_file_ops _serial_fops =
    {
        serial_fops_open,
        serial_fops_close,
        serial_fops_ioctl,
        serial_fops_read,
        serial_fops_write,
        RT_NULL, /* flush */
        RT_NULL, /* lseek */
        RT_NULL, /* getdents */
        serial_fops_poll,
};

/*
 * serial register
 */
int rt_hw_serial_register(struct rt_serial_device *serial,
                          const char *name,
                          int flag,
                          void *data)
{
    int ret;
    struct rt_device *device;

    RT_ASSERT(serial != RT_NULL);

    device = &(serial->parent);

    device->type = RT_Device_Class_Char;
    device->user_data = data;
#ifdef RT_USING_POSIX_TERMIOS
    serial->config.c_oflag = OPOST | ONLCR;
#endif
    /* register a character device */
    ret = rt_device_register(device, name, flag);

    /* set fops */
    device->fops = &_serial_fops;
    serial->debug = 0;
    serial->overrun = 0;

    return ret;
}

/* ISR for serial interrupt */
void rt_hw_serial_isr(struct rt_serial_device *s, int event)
{
    switch (event & 0xff)
    {
    case RT_SERIAL_EVENT_RX_IND:
    {
        uint8_t buf[1];

        while (1)
        {
            int ret = s->ops->get(s);

            if (ret == -1)
                break;
            buf[0] = ret;

            ret = rt_ringbuffer_put(s->rxrb, buf, 1);
            if (ret == 0 && !s->debug)
            {
                s->debug = 0x1;
                LOG_W("%s rxbuf full\n", s->parent.parent.name);
            }
        }

        rt_wqueue_wakeup(&s->rxwq, (void *)POLLIN);
    }
    break;
    case RT_SERIAL_EVENT_TX_DONE:
    {
        uint8_t buf[1];

        if (rt_ringbuffer_get(s->txrb, buf, 1) == 0)
        {
            _set_tx_enable(s, 0);
        }
        else
        {
            s->ops->put(s, buf[0]);
        }

        if (rt_ringbuffer_data_len(s->txrb) <= 1)
        {
            rt_wqueue_wakeup(&s->txwq, (void *)POLLOUT);
        }
    }
    break;
    case RT_SERIAL_EVENT_OVERRUN:
    {
        s->overrun++;
        if (s->overrun == 1)
        {
            LOG_W("%s overrun\n", s->parent.parent.name);
        }
    }
    break;
    }
}
#endif
